import gnu.prolog.database.PrologTextLoaderError;
import gnu.prolog.term.AtomTerm;
import gnu.prolog.term.CompoundTerm;
import gnu.prolog.term.CompoundTermTag;
import gnu.prolog.term.IntegerTerm;
import gnu.prolog.term.Term;
import gnu.prolog.term.TermCloneContext;
import gnu.prolog.term.VariableTerm;
import gnu.prolog.vm.Environment;
import gnu.prolog.vm.Interpreter;
import gnu.prolog.vm.PrologException;

import java.awt.Point;
import java.awt.event.MouseEvent;
import java.rmi.server.ExportException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;


public class PlayerLogic {

	private static Game myGame;
	private Environment prEnv;
	private Interpreter prIntrpr;
	private int boardWidth,
				boardHeight;
	private boolean flaggedSomeFields = false,
					exploredSomeFields = false;
	
	private int board[][],
				xOffset = 0,
				yOffset = 0;
	
	public PlayerLogic(Game game) {
		myGame = game;
		setupProlog();		

	}
	
	public boolean runPrologPlayer(){
		boardWidth = myGame.getBoardWidth();
		boardHeight = myGame.getBoardHeight();
		
		//Current strategy: first find a relevantly big flat or die
		if(!clickUntilFlatRevealed())	return false;
		initBoard();
		//printPrologBoard();
		
		//try{
			while(!myGame.checkIfExploded() && myGame.gameRunning){
				do{
					setFlag();
					//pause(500);
					do if(!myGame.checkIfExploded() && myGame.gameRunning){
						explore();
						//pause(500);
					}while(exploredSomeFields	&&	!myGame.checkIfExploded() && myGame.gameRunning);
				}    while(flaggedSomeFields	&&	!myGame.checkIfExploded() && myGame.gameRunning);
				clickRandom();
			}
		//}catch(IndexOutOfBoundsException e){
			//System.out.println("Won game!");
		//}
		return true;
	}
	
	private void initBoard(){
		int	neighbours = 0;
		board = new int[boardWidth][boardHeight];
		
		
		for(int y=0;y<boardHeight;y++)
			for(int x=0;x<boardWidth;x++)
				board[x][y] = myGame.getCell(new Point(x,y)).getPrologVersionOfCell();
	}
	
	private void setupProlog(){
		prEnv = new Environment();
		
		//Load prolog file
		prEnv.ensureLoaded(AtomTerm.get("./src/ki.pro"));
		
		prIntrpr = prEnv.createInterpreter();
		
		prEnv.runInitialization(prIntrpr);
		
		// Check to see if there were any errors while running the initialization
		// (syntax errors etc.)
		List<PrologTextLoaderError> loadingErrors = prEnv.getLoadingErrors();
		if (loadingErrors.size() > 0)
		{
		System.err.println(loadingErrors);
		return;
		} 
	}
	
	private Term createPrologList(){
		
		//shrink the board-size if possible (remove areas without any unexplored fields
		int xMin = boardWidth,
			xMax = 0,
			yMin = boardHeight,
			yMax = 0;
		
		for(int y=0;y<boardHeight;y++){
			for(int x=0;x<boardWidth;x++){
				if(board[x][y] == -1){
					if(xMin > x)	xMin = x;
					if(xMax < x)	xMax = x;
					if(yMin > y)	yMin = y;
					if(yMax < y)	yMax = y;
				}
			}
		}
		
		//ensure that the neighboured numbers are on the board
		if(xMin > 0)				xMin--;
		if(xMax < (boardWidth-1))	xMax++;
		if(yMin > 0)				yMin--;
		if(yMax < (boardHeight-1))	yMax++;
		
		//Ensure that the board has min. 4x4 fields
		if((xMax - xMin) < 2){
			if(xMax < (boardWidth-2))	xMax += 2;
			else						xMin -= 2;
		}
		
		if((yMax - yMin) < 2){
			if(yMax < (boardHeight-2))	yMax += 2;
			else						yMin -= 2;
		}
		
		xOffset = xMin;
		yOffset = yMin;
		
		ArrayList<Term> row;
		ArrayList<Term> rows = new ArrayList<Term>();
		for(int y=yMin;y<=yMax;y++){
			row = new ArrayList<Term>();
			for(int x=xMin;x<=xMax;x++){
				row.add(new IntegerTerm(board[x][y]));
			}
			rows.add(CompoundTerm.getList(row));
		}
		
		return CompoundTerm.getList(rows);
	}
	
	private void setFlag(){
		
		flaggedSomeFields = false;
		
		Term boardProlog = createPrologList();
		
		VariableTerm varTerm = new VariableTerm("X");
		
		Term[] args = new Term[2];
		ArrayList<Term> resultList = new ArrayList<Term>();
		args[0] = boardProlog;
		args[1] = varTerm;
		CompoundTerm compTerm = new CompoundTerm("getFieldsToFlag", args);
		
		try {
			prIntrpr.runOnce(compTerm);
		} catch (PrologException e) {
			// TODO Auto-generated catch block
			System.out.println("Message: " + e.getMessage());
			System.out.println("Term: " + e.getTerm().toString());
		}
		
		//System.out.println("Ergebnis: " + args[1].toString());
		
		CompoundTerm.toCollection(args[1], resultList);
		
		Point flagHere;

		for(int i=0; i<resultList.size();i+=2){
			//Index in our prolog array starts at 1, in java at 0 ->  (-1) needed!
			int x = ((IntegerTerm) resultList.get(i+1)).value-1,
				y = ((IntegerTerm) resultList.get( i )).value-1;
			
			flagHere = new Point(x,y);
			//System.out.println("flag Cell [" + x + "," + y + "]...");
			//pause(200);
			this.clickOnCell(flagHere, MouseEvent.BUTTON2);
			board[x+xOffset][y+yOffset] = -3;
		}
		
		if(!args[1].toString().equals("'[]'")){
			flaggedSomeFields = true;
			reduceNeighbouredMinesCount();
		}
		//System.out.println(flaggedSomeFields);
		
	}
	
	private void explore(){
		
		exploredSomeFields = false;
		
		Term boardProlog = createPrologList();
		//System.out.println(boardProlog.toString());
		
		VariableTerm varTerm = new VariableTerm("X");
		
		Term[] args = new Term[2];
		ArrayList<Term> resultList = new ArrayList<Term>();
		args[0] = boardProlog;
		args[1] = varTerm;
		CompoundTerm compTerm = new CompoundTerm("getFieldsToExplore", args);
		
		try {
			prIntrpr.runOnce(compTerm);
		} catch (PrologException e) {
			// TODO Auto-generated catch block
			System.out.println("Message: " + e.getMessage());
			System.out.println("Term: " + e.getTerm().toString());
		}
		
		//System.out.println("Ergebnis: " + args[1].toString());
		
		CompoundTerm.toCollection(args[1], resultList);
		
		Point clickHere;
		for(int i=0; i<resultList.size();i+=2){
			//Index in our prolog array starts at 1, in java at 0 ->  (-1) needed!
			int x = ((IntegerTerm) resultList.get(i+1)).value-1,
				y = ((IntegerTerm) resultList.get( i )).value-1;
			
			clickHere = new Point(x,y);
			//System.out.println("explore cell [" + x + "," + y + "]...");
			//pause(200);
			this.clickOnCell(clickHere, MouseEvent.BUTTON1);
		}
		
		if(!args[1].toString().equals("'[]'")){
			exploredSomeFields = true;
			updatePrologBoard_exploring();
		}
		
		//System.out.println(flaggedSomeFields);
		
	}

	private Point getCoordinatesOfCell(Point cell){
		int x=0,
			y=0;
		
		x = Game.PIXELS_OUTER_NW_SLOPE + Game.PIXELS_NW_SURFACE + Game.PIXELS_INNER_FIELD_SLOPE + (cell.x * Cell.WIDTH);
		y = myGame.getTotalHeight() - Game.PIXELS_SE_SURFACE - Game.PIXELS_INNER_FIELD_SLOPE - 
				(boardHeight * Cell.HEIGHT) + (cell.y * Cell.HEIGHT);
		
		return new Point(x,y);
	}
	
	public void clickOnCell(Point cellIndex, int button){
		cellIndex.x += xOffset;
		cellIndex.y += yOffset;
		int x = getCoordinatesOfCell(cellIndex).x,
			y = getCoordinatesOfCell(cellIndex).y;	
		
		System.out.println("Click on cell [" + cellIndex.x + "," + cellIndex.y + "]; Offset is [" + xOffset + "," + yOffset + "]");
		
		//Mouse down
		MouseEvent mouseDownEvent = new MouseEvent(myGame, MouseEvent.MOUSE_PRESSED,
											   System.currentTimeMillis(), 0,
											   x, y,
											   1, false,
											   button);
		
		//Mouse up
		MouseEvent mouseUpEvent = new MouseEvent(myGame, MouseEvent.MOUSE_RELEASED,
				   System.currentTimeMillis(), 0,
				   x, y,
				   1, false,
				   button);
		
		myGame.dispatchEvent(mouseDownEvent);
		
		pause(50);
		
		myGame.dispatchEvent(mouseUpEvent);
	}
	
	public boolean clickUntilFlatRevealed(){
		Random r = new Random();
		Point cell;
		
		System.out.println("*****************************************************************************");
		System.out.println("Stuck... Click on random unexplored field! This might be very dangerous...");
		
		//click while no mine is hit and no big area is revealed
		do{
			//pause(1000);
			cell = new Point(r.nextInt(boardWidth),
							 r.nextInt(boardHeight));
			clickOnCell(cell, MouseEvent.BUTTON1);
		}while(!myGame.checkIfExploded() &&
				myGame.getCell(cell).getNumborOfNeighbourMines() > 0);
		
		System.out.println("*****************************************************************************");
		
		//is the game already over?
		if(!myGame.checkIfExploded() && myGame.gameRunning)		return true;
		else								return false;
	}
	
	private void clickRandom(){
		
		Term boardProlog = createPrologList();
		//System.out.println(boardProlog.toString());
		
		if(boardProlog.toString().equals("'[]'"))	return;
		
		System.out.println("*****************************************************************************");
		System.out.println("Stuck... Click on random unexplored field! This might be very dangerous...");
		
		VariableTerm varTerm = new VariableTerm("X");
		
		Term[] args = new Term[2];
		ArrayList<Term> resultList = new ArrayList<Term>();
		args[0] = boardProlog;
		args[1] = varTerm;
		CompoundTerm compTerm = new CompoundTerm("getRandomUnexploredField", args);
		
		try {
			prIntrpr.runOnce(compTerm);
		} catch (PrologException e) {
			// TODO Auto-generated catch block
			System.out.println("Message: " + e.getMessage());
			System.out.println("Term: " + e.getTerm().toString());
		}
		
		CompoundTerm.toCollection(args[1], resultList);
		//System.out.println("result(random, Java): " + resultList.toString());
		
		Point clickHere;
		//Index in our prolog array starts at 1, in java at 0 ->  (-1) needed!
		int x = ((IntegerTerm) resultList.get(1)).value-1,
			y = ((IntegerTerm) resultList.get(0)).value-1;
		
		clickHere = new Point(x,y);
		System.out.println("explore cell [" + x + "," + y + "]...");
		System.out.println("*****************************************************************************");
		//pause(200);
		this.clickOnCell(clickHere, MouseEvent.BUTTON1);

		exploredSomeFields = true;
		updatePrologBoard_exploring();
	}
	
	public void pause(int ms){
		//seems to be not the right way to pause the execution
				try{
					Thread.currentThread().sleep(ms);
				}catch (Exception e) {
					System.out.println("wait failed in method clickOnCell: " + e.getMessage());
				}
	}
	
	public void printPrologBoard(){
		for(int y=0;y<boardHeight;y++){	
			for(int x=0;x<boardWidth;x++){
				System.out.print("[");
				if(board[x][y]>=0)	System.out.print(" ");
				System.out.print(board[x][y] + "]");
			}
			System.out.println("");
		}
	}
	
	private void reduceNeighbouredMinesCount(){
		//System.out.println("Vorher: ");
		//printPrologBoard();
		for(int x=0;x<boardWidth;x++){
			for(int y=0;y<boardHeight;y++){
				if(board[x][y] == -3){
					for(int j=-1;j<2;j++)
						for(int i=-1;i<2;i++)	if( ((x+i)>=0) &&
													((x+i)< boardWidth) &&
													((y+j)>=0) &&
													((y+j)< boardHeight))
												   		if(board[x+i][y+j]>0) {
												   			board[x+i][y+j]--;
												   			board[x][y] = -2;
												   		}
				}
			}
		}
		//System.out.println("");
		//System.out.println("Nachher:");
		//printPrologBoard();
	}
	
	private void updatePrologBoard_exploring() {
		//System.out.println("Vorher: ");
		//printPrologBoard();
		for(int x=0;x<boardWidth;x++){
			for(int y=0;y<boardHeight;y++){
				if(board[x][y] == -1){
					board[x][y] = myGame.getCell(new Point(x,y)).getPrologVersionOfCell();
					//substract already found bombs from neighboured-bomb-count
					if(board[x][y]>0)
						for(int j=-1;j<2;j++)
							for(int i=-1;i<2;i++)	if( ((x+i)>=0) &&
														((x+i)< boardWidth) &&
														((y+j)>=0) &&
														((y+j)< boardHeight))
													   		if(board[x+i][y+j]==-2) {
													   			board[x][y]--;
													   		}
				}
			}
		}
		//System.out.println("");
		//System.out.println("Nachher:");
		//printPrologBoard();
		
	}
	
	
	
}
